package vm

import (
	"go-forth/pkg/forth"
)

type Word interface {
	Append(...forth.Token)
	MarkImmediate()
}

type codeWord struct {
	name string // debug only
	data *DataSpace

	// Immediate words are executed immediately
	Immediate bool
	// Run the native code or execute the tokens sequentially
	Run func(forth.StackVM)
	// Address of the data field
	Address int
	// Length is the number of instructions so no need to use placeholder
	Length int
}

func (w *codeWord) Name() string {
	return w.name
}

func (w *codeWord) RunOn(vm forth.StackVM) {
	if w.Run != nil { // native word
		w.Run(vm)
		return
	}
	// DOCOL
	to := w.Address + w.Length
	vm.ReturnStack().Push(forth.NestSys(to))
	vm.Execute(w.Address, to)
}

func (w *codeWord) Location() int {
	return w.Address
}

func (w *codeWord) Append(tokens ...forth.Token) {
	for _, t := range tokens {
		w.data.Append(t)
		w.Length++
	}
}

func (w *codeWord) MarkImmediate() {
	w.Immediate = true
}

var _ forth.ExecutionToken = &codeWord{}

type Table struct {
	words map[string][]*codeWord
	data  *DataSpace
}

func NewTable(data *DataSpace) *Table {
	return &Table{
		words: make(map[string][]*codeWord),
		data:  data,
	}
}

func (d *Table) Register(name string, immediate bool, tokens ...func(forth.StackVM)) Word {
	ret := &codeWord{
		name:      name,
		data:      d.data,
		Immediate: immediate,
		Address:   d.data.Reserved(),
	}
	switch len(tokens) {
	case 0:
		// colon
	case 1:
		// consists of native code
		ret.Run = tokens[0]
	default:
		// consts of native subroutines
		for _, token := range tokens {
			d.data.Append(token)
			ret.Length++
		}
	}
	d.words[name] = append(d.words[name], ret)
	return ret
}

func (d *Table) Find(name string) (forth.ExecutionToken, bool, bool) {
	words, found := d.words[name]
	if !found || len(words) == 0 {
		return nil, false, false
	}
	w := words[len(words)-1]
	return w, w.Immediate, true
}
